/***************************************************************************
 *   Copyright (C) 2010 by programmeboy                                    *
 *   programmeboy@gmail.com                                                *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.                                        *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             * 
 ***************************************************************************/

#include "stdafx.h"

#include <stdio.h>
#include <time.h>
#include <tchar.h>
#include <Windows.h>

#include "log.h"

#ifdef WIN32
#pragma warning(disable:4996)
#endif // _DEBUG

using namespace WCMM;

static LPCTSTR g_level_desc[] = {
    _T("[INFO]"),
    _T("[WARNING]"),
    _T("[FAILED]"),
	_T("[ERROR]")
};

#define COLOR_ERROR		FOREGROUND_RED
#define COLOR_WARNING	FOREGROUND_GREEN | FOREGROUND_RED
#define COLOR_FAILED	FOREGROUND_RED
#define COLOR_INFO		FOREGROUND_GREEN
#define COLOR_DEFAULT	FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE

CLogging::CLogging(VOID):m_LogFile(NULL),
                         m_LogMode(MODE_STDOUT),
                         m_Level(LEVEL_ALL),
						 m_bEnbaleColor(TRUE),
						 m_Color(COLOR_DEFAULT)
{
    LogInitialize();
}

CLogging::CLogging(IN LPCTSTR lpszLogFile):m_Level(LEVEL_ALL), m_bEnbaleColor(TRUE)
{
    m_strLogFile = lpszLogFile;
    m_LogMode = MODE_FILE;
    LogInitialize();
}

CLogging::~CLogging(VOID)
{
    DeleteCriticalSection(&m_cs);
	LogFree();
}


FORCEINLINE
BOOL CLogging::SetConsoleColor(WORD wAttributes)
{
	if (m_Color == wAttributes){
		return TRUE;
	}else{
		m_Color = wAttributes;
	}
	
	HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
	if (hConsole == INVALID_HANDLE_VALUE)
		return FALSE;

	return SetConsoleTextAttribute(hConsole, wAttributes);
}

FORCEINLINE
BOOL CLogging::SetConsoleColorByLevel(LOGGING_LEVEL Level)
{
	WORD wAttributes;

	switch (Level)
	{
	case LEVEL_INFO:
		wAttributes = COLOR_INFO;
		break;
	case LEVEL_FAILED:
		wAttributes = COLOR_FAILED;
		break;
	case LEVEL_ERROR:
		wAttributes = COLOR_ERROR;
		break;
	case LEVEL_WORNING:
		wAttributes = COLOR_WARNING;
		break;
	default:
		wAttributes = COLOR_DEFAULT;
		break;
	}

	return SetConsoleColor(wAttributes);
}

FORCEINLINE
BOOL CLogging::ResetConsoleColor()
{
	return SetConsoleColor(COLOR_DEFAULT);
}

VOID
CLogging::SetEnableColor(BOOL bEnbale)
{
	m_bEnbaleColor = bEnbale;
}

FORCEINLINE
BOOL
CLogging::IsShowColor()
{
	return m_LogMode == MODE_STDOUT && m_bEnbaleColor;
}

VOID
CLogging::SetLogLevel(
    IN ULONG Level
)
/*++

Routine Description:

    The level is like :
    LEVEL_INFO
    LEVEL_INFO|LEVEL_WORNING
    LEVEL_FAILED|LEVEL_ERROR
    ...

Arguments:

	

Return Value:

	None

--*/
{
    m_Level = Level;
}

BOOL
CLogging::SetLoggingFile(
    IN LPCTSTR lpszLogFile
)
{
    m_strLogFile = lpszLogFile;
    m_LogMode = MODE_FILE;
    m_LogFile = NULL;

    //
    // call init.
    //
    
    return LogInitialize();

}

BOOL
CLogging::LogInitialize(
    VOID
)
/*++

Routine Description:

	initialize the logging function

Arguments:

	None

Return Value:

    None

--*/
{
	InitializeCriticalSection(&m_cs);
    if (m_LogFile != NULL)
    	return TRUE;
    if (m_LogMode == MODE_FILE && !m_strLogFile.IsEmpty()){
        m_LogFile = _tfopen( m_strLogFile.GetBuffer(0), _T("a+"));
        if ( m_LogFile == NULL ){
            return FALSE;
        }
    }else{
        m_LogFile = stdout;
    }
    return TRUE;
}

BOOL 
CLogging::LogHexDump(
    IN LOGGING_LEVEL Level,
    IN PVOID Buffer, 
    IN ULONG Length
)
{
	int i, j;
    PUCHAR TmpBuffer = (PUCHAR)Buffer;

    if (!m_LogFile){
        return FALSE;
    }

    if (!CheckLevel(Level)){
        return TRUE;
    }
	
	EnterCriticalSection(&m_cs);

    //
    //  show time.
    //
    
    LogString(Level, _T("-------Data %d bytes------"), Length);
	
	if (IsShowColor()){
		SetConsoleColorByLevel(Level);
	}

	for (i = 0; i < (int)Length; i += 16) {
		_ftprintf(m_LogFile, TEXT("%06x: "), i);
		for (j = 0; j < 16; j ++) 
			if (i + j < (int)Length)
				_ftprintf(m_LogFile, TEXT("%02x "), TmpBuffer[i+j]);
			else
				_ftprintf(m_LogFile,  TEXT("   "));
		_ftprintf(m_LogFile,  TEXT(" "));
		for (j = 0; j < 16; j++) 
			if (i + j < (int)Length)
				_ftprintf(m_LogFile,  TEXT("%c"), isprint(TmpBuffer[i+j]) ? TmpBuffer[i+j] : '.');
		_ftprintf(m_LogFile, TEXT("\n"));
	}

	if (IsShowColor()){
		ResetConsoleColor();
	}

    LogString(Level, _T("--------------------------"), Length);

    fflush(m_LogFile);

	LeaveCriticalSection(&m_cs);

    return TRUE;
}
/** \fn void log_string( char* sz_log )
 *  \brief logging a string to log_file
 *  \param sz_log the log string
 *  \return
 */

BOOL 
CLogging::LogString(
    IN LOGGING_LEVEL Level, 
    IN LPCTSTR lpszFmt, 
    ... 
)
{
    BOOL bRet;
    va_list Args;

    va_start(Args, lpszFmt);
    bRet = LogString(Level, lpszFmt, Args);
    va_end(Args);

    return bRet;
}

FORCEINLINE
ULONG
CLogging::GetLogIndex(
    LOGGING_LEVEL Level
)
{
    ULONG Index;
    for (Index = 0; Index < 100; Index++){
        if ((1 << Index) == Level){
            return Index;
        }
    }
    return (ULONG)-1;
}

BOOL 
CLogging::LogString(
    IN LOGGING_LEVEL Level, 
    IN LPCTSTR lpszFmt, 
    IN va_list Args 
)
{
    time_t Timep;
    struct tm* t_p;
    LPTSTR szSprintf = NULL;
    int nLen;
    ULONG Index;

    if (!m_LogFile){
        return FALSE;
    }

    if (!CheckLevel(Level)){
        return TRUE;
    }

    Index = GetLogIndex(Level);
    if (Index == -1){
        return FALSE;
    }

    nLen = _vsntprintf(NULL, 0, lpszFmt, Args);
    szSprintf = (TCHAR*)new TCHAR[nLen + 1];
    _vsntprintf(szSprintf, nLen + 1, lpszFmt, Args);

    //
    // get time
    //

    time( &Timep );
    t_p = localtime( &Timep );
	EnterCriticalSection(&m_cs);

	if (IsShowColor()){
		SetConsoleColorByLevel(Level);
	}

	_ftprintf( m_LogFile, _T("[ %04d-%02d-%02d %02d:%02d:%02d ] %s %s\n"), 
                t_p->tm_year+1900,t_p->tm_mon+1, \
				t_p->tm_mday, t_p->tm_hour, t_p->tm_min, t_p->tm_sec, \
				g_level_desc[Index], szSprintf );
    fflush(m_LogFile);

	if (IsShowColor()){
		ResetConsoleColor();
	}

	LeaveCriticalSection(&m_cs);
    delete szSprintf;

    return TRUE;
}

VOID 
CLogging::LogFree(
    VOID
)
{
	if (m_LogFile == NULL)
		return;
    if (m_LogMode == MODE_FILE){
        fclose( m_LogFile );
    }
	m_LogFile = NULL;
}


/** \fn 
    \brief check the level is want to show?
    \return 
*/

BOOL
CLogging::CheckLevel(
    IN LOGGING_LEVEL Level
)
{
    if (m_Level & Level){
        return TRUE;
    }
    return FALSE;
}

CLogging& CLogging::GetInstance()
{
	static CLogging Instance;
	return Instance;
}